#!/usr/bin/env python3
import sys
import os
import subprocess
import threading
import json
import time
import shutil
import zipfile
import logging
import uuid as _uuid_mod
from pathlib import Path
from datetime import datetime

log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "emce.log")
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    handlers=[
        logging.FileHandler(log_path, encoding="utf-8"),
        logging.StreamHandler(sys.stdout),
    ]
)
logger = logging.getLogger("DustMC")
logger.info(f"=== DustMC Launcher started === Log file: {log_path}")

import minecraft_launcher_lib
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QStackedWidget, QPushButton, QLabel, QLineEdit, QComboBox,
    QTextEdit, QFileDialog, QMessageBox, QFrame, QSpinBox,
    QCheckBox, QGroupBox, QListWidget, QListWidgetItem,
    QProgressBar, QTabWidget, QScrollArea, QSplitter
)
from PyQt6.QtCore import Qt, pyqtSignal, QObject, QThread, QUrl
from PyQt6.QtGui import QFont, QPixmap, QTextCursor, QColor, QTextCharFormat
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply

class ModernColors:
    BACKGROUND = "#0A0A0A"
    SURFACE = "#1A1A1A"
    SURFACE_LIGHT = "#252525"
    PRIMARY = "#4ADE80"
    PRIMARY_DARK = "#22C55E"
    SECONDARY = "#3B82F6"
    ACCENT = "#8B5CF6"
    TEXT = "#FFFFFF"
    TEXT_SECONDARY = "#A0A0A0"
    BORDER = "#333333"
    ERROR = "#EF4444"
    WARNING = "#F59E0B"
    SUCCESS = "#10B981"
    CONSOLE_BLACK = "#0A0A0A"
    CONSOLE_WHITE = "#FFFFFF"
    CONSOLE_GREEN = "#4ADE80"
    CONSOLE_RED = "#EF4444"
    CONSOLE_YELLOW = "#F59E0B"

    PRIMARY_GRADIENT = "qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #4ADE80, stop:1 #22C55E)"

class ImageLoader(QObject):
    finished = pyqtSignal(QPixmap, int)

    def __init__(self, url, index):
        super().__init__()
        self.url = url
        self.index = index
        self.manager = QNetworkAccessManager()
        self.manager.finished.connect(self.on_finished)

    def start(self):
        request = QNetworkRequest(QUrl(self.url))
        self.manager.get(request)

    def on_finished(self, reply):
        if reply.error() == QNetworkReply.NetworkError.NoError:
            data = reply.readAll()
            pixmap = QPixmap()
            pixmap.loadFromData(data)
            self.finished.emit(pixmap, self.index)
        else:
            self.finished.emit(QPixmap(), self.index)
        reply.deleteLater()

class SignalEmitter(QObject):
    status_update = pyqtSignal(str)
    progress_update = pyqtSignal(int)
    installation_finished = pyqtSignal()
    versions_loaded = pyqtSignal(list)
    news_loaded = pyqtSignal(list)
    console_output = pyqtSignal(str)

class InstallWorker(QThread):
    def __init__(self, loader, version, base_dir, emitter):
        super().__init__()
        self.loader = loader
        self.version = version
        self.base_dir = base_dir
        self.emitter = emitter

    def run(self):
        try:
            def update_progress(progress):
                self.emitter.progress_update.emit(int(progress * 100))

            self.loader.install(
                self.version,
                self.base_dir,
                callback={
                    "setStatus": self.emitter.status_update.emit,
                    "setProgress": update_progress
                }
            )
            self.emitter.installation_finished.emit()
        except Exception as e:
            self.emitter.status_update.emit(f"Error: {str(e)}")

class ModpackInstallWorker(QThread):
    """
    MultiMC/Modrinth-style containerized modpack installer.
    Setiap modpack punya instance folder sendiri yang berisi:
      <instances_dir>/<pack_name>/
        ├── mods/
        ├── config/
        ├── resourcepacks/
        ├── shaderpacks/
        ├── saves/
        ├── screenshots/
        └── instance.json   (metadata modpack)
    Library & assets tetap di base_dir (.minecraft).
    """
    def __init__(self, file_path, base_dir, instances_dir, emitter, java_path=None):
        super().__init__()
        self.file_path = file_path
        self.base_dir = base_dir
        self.instances_dir = instances_dir
        self.emitter = emitter
        self.java_path = java_path or "javaw"

    def _create_instance_structure(self, instance_path):
        """Buat struktur folder ala MultiMC untuk satu instance."""
        subdirs = ["mods", "config", "resourcepacks", "shaderpacks",
                   "saves", "screenshots", "logs"]
        for d in subdirs:
            path = os.path.join(instance_path, d)
            os.makedirs(path, exist_ok=True)
            logger.debug(f"[WRITE] Created subdir: {path}")

    def _write_instance_json(self, instance_path, meta):
        """Tulis metadata instance ke instance.json."""
        instance_json = os.path.join(instance_path, "instance.json")
        with open(instance_json, "w", encoding="utf-8") as f:
            json.dump(meta, f, indent=2)
        logger.info(f"[WRITE] instance.json written: {instance_json}")

    def run(self):
        try:
            if self.file_path.endswith('.mrpack'):
                self.install_mrpack()
            elif self.file_path.endswith('.jar'):
                self.install_jar_as_instance()
        except Exception as e:
            logger.error(f"[ERROR] ModpackInstallWorker.run: {e}")
            self.emitter.status_update.emit(f"Error: {str(e)}")

    def install_mrpack(self):
        try:
            self.emitter.status_update.emit("Loading mrpack information...")
            logger.info(f"[READ] ── Reading mrpack ─────────────────────────────────────")
            logger.info(f"[READ]   File: {self.file_path}")
            info = minecraft_launcher_lib.mrpack.get_mrpack_information(self.file_path)

            pack_name = info.get('name', os.path.basename(self.file_path).replace('.mrpack', ''))
            pack_version = info.get('version', 'unknown')
            mc_version = info.get('minecraftVersion', 'unknown')
            logger.info(f"[READ]   Pack name  : {pack_name}")
            logger.info(f"[READ]   Pack ver   : {pack_version}")
            logger.info(f"[READ]   MC version : {mc_version}")

            instance_path = os.path.join(self.instances_dir, pack_name)
            logger.info(f"[WRITE] ── Creating instance ──────────────────────────────────")
            logger.info(f"[WRITE]   Path: {instance_path}")
            self._create_instance_structure(instance_path)

            self.emitter.status_update.emit(f"Installing {pack_name} {pack_version}...")
            logger.info(f"[WRITE] ── Running mrpack installer ────────────────────────────")
            logger.info(f"[WRITE]   Base dir     : {self.base_dir}")
            logger.info(f"[JAVA]    Java exe      : {self.java_path}")

            java_bin_dir = os.path.dirname(self.java_path)
            original_path = os.environ.get("PATH", "")
            if os.path.isdir(java_bin_dir) and java_bin_dir not in original_path:
                os.environ["PATH"] = java_bin_dir + os.pathsep + original_path
                logger.info(f"[JAVA] Prepended bin dir to PATH: {java_bin_dir}")
            else:
                logger.debug(f"[JAVA] PATH not modified (not a dir or already present): {java_bin_dir}")

            options = {"optionalFiles": []}

            logger.info(f"[WRITE] Calling minecraft_launcher_lib.mrpack.install_mrpack ...")
            minecraft_launcher_lib.mrpack.install_mrpack(
                self.file_path,
                self.base_dir,
                modpack_directory=instance_path,
                mrpack_install_options=options,
                callback={
                    "setStatus": self.emitter.status_update.emit,
                    "setProgress": lambda p: self.emitter.progress_update.emit(int(p * 100))
                }
            )
            logger.info(f"[DONE] minecraft_launcher_lib.mrpack.install_mrpack completed")

            self._write_instance_json(instance_path, {
                "name": pack_name,
                "version": pack_version,
                "minecraftVersion": mc_version,
                "type": "mrpack",
                "sourceFile": self.file_path,
                "installedAt": datetime.now().isoformat()
            })

            logger.info(f"[DONE] mrpack '{pack_name}' fully installed to {instance_path}")
            self.emitter.status_update.emit(f"Done: {pack_name} installed to {instance_path}")
            self.emitter.installation_finished.emit()

        except Exception as e:
            logger.error(f"[ERROR] install_mrpack failed: {e}", exc_info=True)
            self.emitter.status_update.emit(f"Error installing mrpack: {str(e)}")

    def install_jar_as_instance(self):
        """
        Install JAR mod sebagai instance tersendiri.
        JAR diletakkan di <instances_dir>/<mod_name>/mods/<file>.jar
        """
        try:
            self.emitter.status_update.emit("Installing JAR mod as instance...")
            logger.info(f"[READ] Reading JAR file: {self.file_path}")

            mod_name = os.path.basename(self.file_path).replace('.jar', '')
            instance_path = os.path.join(self.instances_dir, mod_name)
            logger.info(f"[WRITE] Creating instance for JAR mod at: {instance_path}")
            self._create_instance_structure(instance_path)

            dest_path = os.path.join(instance_path, "mods", os.path.basename(self.file_path))
            logger.info(f"[WRITE] Copying JAR: {self.file_path} → {dest_path}")
            shutil.copy2(self.file_path, dest_path)

            self._write_instance_json(instance_path, {
                "name": mod_name,
                "version": "unknown",
                "minecraftVersion": "unknown",
                "type": "jar",
                "sourceFile": self.file_path,
                "installedAt": datetime.now().isoformat()
            })

            logger.info(f"[DONE] JAR mod '{mod_name}' installed to {instance_path}")
            self.emitter.status_update.emit(f"✓ Mod {mod_name} installed as instance at {instance_path}")
            self.emitter.installation_finished.emit()

        except Exception as e:
            logger.error(f"[ERROR] install_jar_as_instance failed: {e}")
            self.emitter.status_update.emit(f"Error installing JAR: {str(e)}")

class NewsWorker(QThread):
    def __init__(self, emitter):
        super().__init__()
        self.emitter = emitter

    def run(self):
        try:
            news = [
                {"title": "Minecraft 1.20.5 Released!", "date": "2024-03-15"},
                {"title": "New Modding API Announced", "date": "2024-03-10"},
                {"title": "Community Event: Build Contest", "date": "2024-03-05"}
            ]
            time.sleep(1)
            self.emitter.news_loaded.emit(news)
        except:
            pass

class MinecraftProcess(QThread):
    def __init__(self, command, work_dir, emitter):
        super().__init__()
        self.command = command
        self.work_dir = work_dir
        self.emitter = emitter
        self.process = None

    def run(self):
        try:
            self.process = subprocess.Popen(
                self.command,
                cwd=self.work_dir,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1
            )

            for line in self.process.stdout:
                self.emitter.console_output.emit(line.rstrip())

            self.process.wait()
            self.emitter.console_output.emit(f"Process finished with code: {self.process.returncode}")

        except Exception as e:
            self.emitter.console_output.emit(f"Error: {str(e)}")

    def stop(self):
        if self.process:
            self.process.terminate()

class ModernButton(QPushButton):
    def __init__(self, text, parent=None, primary=False):
        super().__init__(text, parent)
        self.setMinimumHeight(30)
        self.setCursor(Qt.CursorShape.PointingHandCursor)

        if primary:
            self.setStyleSheet(f"""
                QPushButton {{
                    background: {ModernColors.PRIMARY_GRADIENT};
                    color: {ModernColors.BACKGROUND};
                    border: none;
                    border-radius: 4px;
                    padding: 5px 14px;
                    font-weight: 600;
                    font-size: 11px;
                }}
                QPushButton:hover {{
                    background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0
                }}
                QPushButton:pressed {{
                    background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0
                }}
            """)
        else:
            self.setStyleSheet(f"""
                QPushButton {{
                    background-color: {ModernColors.SURFACE_LIGHT};
                    color: {ModernColors.TEXT};
                    border: 1px solid {ModernColors.BORDER};
                    border-radius: 4px;
                    padding: 5px 14px;
                    font-weight: 500;
                    font-size: 11px;
                }}
                QPushButton:hover {{
                    background-color: {ModernColors.SURFACE};
                    border: 1px solid {ModernColors.PRIMARY};
                }}
                QPushButton:pressed {{
                    background-color: {ModernColors.BORDER};
                }}
                QPushButton:checked {{
                    background: {ModernColors.PRIMARY_GRADIENT};
                    color: {ModernColors.BACKGROUND};
                    border: none;
                }}
            """)

class SidebarButton(QPushButton):
    def __init__(self, text, parent=None):
        super().__init__(text, parent)
        self.setMinimumHeight(34)
        self.setCursor(Qt.CursorShape.PointingHandCursor)
        self.setCheckable(True)

        self.setStyleSheet("""
            QPushButton {
                text-align: left;
                padding: 7px 14px;
                border: none;
                border-radius: 4px;
                font-size: 11px;
                font-weight: 500;
                margin: 1px 6px;
                background-color: transparent;
                color:
            }
            QPushButton:hover {
                background-color: rgba(255, 255, 255, 0.05);
                color:
            }
            QPushButton:checked {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 rgba(74, 222, 128, 0.1), stop:1 rgba(34, 197, 94, 0.1));
                color:
            }
        """)

class ConsolePage(QWidget):
    def __init__(self, emitter, log_path, parent=None):
        super().__init__(parent)
        self.emitter = emitter
        self.log_path = log_path
        self.emitter.console_output.connect(self.append_output)
        self.setStyleSheet(f"QWidget {{ background-color: {ModernColors.BACKGROUND}; }}")
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        header = QHBoxLayout()
        title = QLabel("Console Output")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")

        self.open_log_btn = QPushButton("Open Log File")
        self.open_log_btn.setFixedSize(90, 22)
        self.open_log_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.open_log_btn.setStyleSheet(f"""
            QPushButton {{
                background-color: {ModernColors.SURFACE_LIGHT};
                color: {ModernColors.TEXT_SECONDARY};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 3px;
                font-size: 9px;
                font-weight: 500;
            }}
            QPushButton:hover {{
                border: 1px solid {ModernColors.ACCENT};
                color: {ModernColors.TEXT};
            }}
        """)
        self.open_log_btn.clicked.connect(self.open_log_in_terminal)

        self.clear_btn = QPushButton("Clear Console")
        self.clear_btn.setFixedSize(100, 28)
        self.clear_btn.setStyleSheet(f"""
            QPushButton {{
                background-color: {ModernColors.SURFACE_LIGHT};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                font-size: 11px;
                font-weight: 500;
            }}
            QPushButton:hover {{
                border: 1px solid {ModernColors.PRIMARY};
                background-color: {ModernColors.SURFACE};
            }}
        """)
        self.clear_btn.clicked.connect(self.clear_console)

        header.addWidget(title)
        header.addStretch()
        header.addWidget(self.open_log_btn)
        header.addSpacing(6)
        header.addWidget(self.clear_btn)

        layout.addLayout(header)

        self.console = QTextEdit()
        self.console.setReadOnly(True)
        self.console.setStyleSheet(f"""
            QTextEdit {{
                background-color: {ModernColors.CONSOLE_BLACK};
                color: {ModernColors.CONSOLE_WHITE};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 6px;
                font-family: 'Courier New', monospace;
                font-size: 11px;
                padding: 10px;
            }}
        """)

        layout.addWidget(self.console)

        info_label = QLabel("Minecraft console output will appear here when game is running.")
        info_label.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 11px; font-style: italic;")
        info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(info_label)

    def open_log_in_terminal(self):
        """Open the emce.log file in an external Windows terminal window."""
        try:
            if not os.path.isfile(self.log_path):
                open(self.log_path, "a").close()

            cmd = (
                f'start powershell -NoExit -Command '
                f'"Get-Content -Path \'{self.log_path}\' -Wait -Tail 50"'
            )
            subprocess.Popen(cmd, shell=True)
            logger.info(f"[CONSOLE] Opened log in external terminal: {self.log_path}")
        except Exception as e:
            logger.error(f"[ERROR] Failed to open log terminal: {e}")

    def append_output(self, text):
        cursor = self.console.textCursor()
        cursor.movePosition(QTextCursor.MoveOperation.End)
        fmt = QTextCharFormat()

        t = text.upper()
        if any(k in t for k in ("FATAL", "CRASH", "CRITICAL")):
            fmt.setForeground(QColor("#FF4444"))
            fmt.setFontWeight(700)
        elif any(k in t for k in ("ERROR", "EXCEPTION", "FAILED", "FAILURE")):
            fmt.setForeground(QColor(ModernColors.CONSOLE_RED))
        elif any(k in t for k in ("WARN",)):
            fmt.setForeground(QColor(ModernColors.CONSOLE_YELLOW))
        elif any(k in t for k in ("[JAVA]", "[LAUNCH]", "STARTING", "STARTED", "LAUNCHING")):
            fmt.setForeground(QColor(ModernColors.SECONDARY))
        elif any(k in t for k in ("[WRITE]", "INSTALLED", "DOWNLOADING", "EXTRACTING", "COPYING")):
            fmt.setForeground(QColor(ModernColors.ACCENT))
        elif any(k in t for k in ("[READ]", "LOADED", "FOUND", "SCANNING")):
            fmt.setForeground(QColor("#A78BFA"))
        elif any(k in t for k in ("[DELETE]", "REMOVED", "CLEARED", "DELETED")):
            fmt.setForeground(QColor(ModernColors.WARNING))
        elif any(k in t for k in ("INFO", "SUCCESS", "DONE", "COMPLETE", "[DONE]")):
            fmt.setForeground(QColor(ModernColors.CONSOLE_GREEN))
        elif text.startswith("==="):
            fmt.setForeground(QColor(ModernColors.PRIMARY))
            fmt.setFontWeight(700)
        elif text.startswith("Command:"):
            fmt.setForeground(QColor(ModernColors.TEXT_SECONDARY))
        else:
            fmt.setForeground(QColor(ModernColors.CONSOLE_WHITE))

        cursor.insertText(text + "\n", fmt)
        self.console.setTextCursor(cursor)
        self.console.ensureCursorVisible()

    def clear_console(self):
        self.console.clear()
        self.append_output("=== Console cleared ===")

class ImageCard(QFrame):
    def __init__(self, image_url, parent=None):
        super().__init__(parent)
        self.image_url = image_url
        self.original_pixmap = None
        self.setup_ui()
        self.load_image()

    def setup_ui(self):
        self.setStyleSheet(f"""
            QFrame {{
                background-color: {ModernColors.SURFACE};
                border-radius: 5px;
                border: 1px solid {ModernColors.BORDER};
            }}
        """)
        self.setMinimumSize(180, 100)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)

        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.image_label.setStyleSheet(f"background-color: {ModernColors.SURFACE_LIGHT}; border-radius: 5px;")
        layout.addWidget(self.image_label)

    def load_image(self):
        self.loader = ImageLoader(self.image_url, 0)
        self.loader.finished.connect(self.on_image_loaded)
        self.loader.start()

    def on_image_loaded(self, pixmap, index):
        if not pixmap.isNull():
            self.original_pixmap = pixmap
            self.update_image_display()
        else:
            self.image_label.setText("Failed to load")

    def update_image_display(self):
        if self.original_pixmap and not self.original_pixmap.isNull():
            scaled_pixmap = self.original_pixmap.scaled(
                self.image_label.size(),
                Qt.AspectRatioMode.KeepAspectRatio,
                Qt.TransformationMode.SmoothTransformation
            )
            self.image_label.setPixmap(scaled_pixmap)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.update_image_display()

APP_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_PATH = os.path.join(APP_DIR, "config.json")

def _default_minecraft_dir() -> str:
    """Return lokasi .minecraft standar sesuai OS."""
    if sys.platform == "win32":
        return os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), ".minecraft")
    elif sys.platform == "darwin":
        return os.path.expanduser("~/Library/Application Support/minecraft")
    else:
        return os.path.expanduser("~/.minecraft")

DEFAULT_CONFIG = {
    "base_minecraft_dir": _default_minecraft_dir(),
    "instances_dir": os.path.join(_default_minecraft_dir(), "instances"),
    "username": "Player",
    "uuid": "",
    "ram_mb": 2048,
    "width": 854,
    "height": 480,
    "jvm_args": "",
    "auto_show_console": True,
    "use_portable_java": True,
    "last_played_instance": "",
}

def load_config() -> dict:
    """Load config dari config.json. Gunakan default jika file belum ada."""
    if os.path.isfile(CONFIG_PATH):
        try:
            with open(CONFIG_PATH, "r", encoding="utf-8") as f:
                saved = json.load(f)

            cfg = dict(DEFAULT_CONFIG)
            cfg.update(saved)
            logger.info(f"[READ] Config loaded from {CONFIG_PATH}")
        except Exception as e:
            logger.warning(f"[READ] Failed to load config, using defaults: {e}")
            cfg = dict(DEFAULT_CONFIG)
    else:
        cfg = dict(DEFAULT_CONFIG)

    if not cfg.get("uuid"):
        cfg["uuid"] = str(_uuid_mod.uuid4())
        logger.info(f"[WRITE] Generated new player UUID: {cfg['uuid']}")
        save_config(cfg)

    return cfg

def save_config(cfg: dict):
    """Simpan config ke config.json."""
    try:
        with open(CONFIG_PATH, "w", encoding="utf-8") as f:
            json.dump(cfg, f, indent=2, ensure_ascii=False)
        logger.info(f"[WRITE] Config saved to {CONFIG_PATH}")
    except Exception as e:
        logger.error(f"[ERROR] Failed to save config: {e}")

def get_portable_java(base_minecraft_dir: str) -> str:
    """
    Return the path to the portable Java runtime bundled under the launcher directory.
    Falls back to 'javaw' (system PATH) if the portable binary is not found.
    Expected layout: <base_minecraft_dir>/runtime/java-runtime-delta/windows-x64/java-runtime-delta/bin/javaw.exe
    """
    javaw = os.path.join(
        base_minecraft_dir,
        "runtime", "java-runtime-delta", "windows-x64",
        "java-runtime-delta", "bin", "javaw.exe"
    )
    if os.path.isfile(javaw):
        logger.info(f"[JAVA] Using portable runtime: {javaw}")
        return javaw

    java_fallback = os.path.join(
        base_minecraft_dir,
        "runtime", "java-runtime-delta", "windows-x64",
        "java-runtime-delta", "bin", "java.exe"
    )
    if os.path.isfile(java_fallback):
        logger.info(f"[JAVA] Using portable runtime (java.exe): {java_fallback}")
        return java_fallback
    logger.warning(f"[JAVA] Portable runtime not found at expected path, falling back to system java")
    return "javaw"

class DustMinecraftLauncher(QMainWindow):
    def __init__(self):
        super().__init__()

        self.config = load_config()
        self.base_minecraft_dir = self.config["base_minecraft_dir"]
        self.instances_dir = self.config["instances_dir"]
        os.makedirs(self.instances_dir, exist_ok=True)

        self.loader_list = []
        self.installed_versions = []
        self.selected_loader = None
        self.current_mrpack_info = None
        self.minecraft_process = None
        self.image_urls = [
            "https://www.minecraft.net/content/dam/minecraftnet/games/minecraft/key-art/coppergolem-header2.jpg",
            "https://www.minecraft.net/content/dam/minecraftnet/games/minecraft/screenshots/MCV_SpringDrop2026_BPS_Feb17_Editorial1170x500.jpg"
        ]

        self.emitter = SignalEmitter()
        self.setup_signals()
        self.init_ui()
        self._apply_config_to_ui()
        self.load_data()

    def _apply_config_to_ui(self):
        """Terapkan nilai config yang sudah di-load ke widget UI."""
        cfg = self.config
        self.base_dir_edit.setText(self.base_minecraft_dir)
        self.settings_dir.setText(self.base_minecraft_dir)
        self.instances_dir_edit.setText(self.instances_dir)
        self.mrpack_dir.setText(self.instances_dir)
        self.username.setText(cfg.get("username", "Player"))
        self.ram.setValue(cfg.get("ram_mb", 2048))
        self.width.setValue(cfg.get("width", 854))
        self.height.setValue(cfg.get("height", 480))
        self.jvm_args.setText(cfg.get("jvm_args", ""))
        self.auto_show_console.setChecked(cfg.get("auto_show_console", True))
        self.use_portable_java_check.setChecked(cfg.get("use_portable_java", True))
        self.update_last_played_display()

    def setup_signals(self):
        self.emitter.status_update.connect(self.update_status)
        self.emitter.progress_update.connect(self.update_progress)
        self.emitter.installation_finished.connect(self.on_installation_finished)
        self.emitter.versions_loaded.connect(self.update_versions_list)
        self.emitter.news_loaded.connect(self.update_news)

    def init_ui(self):
        self.setWindowTitle("DustMC Launcher")
        self.setMinimumSize(900, 600)

        self.setStyleSheet(f"""
            QMainWindow {{
                background-color: {ModernColors.BACKGROUND};
            }}
            QLabel {{
                color: {ModernColors.TEXT};
                font-size: 11px;
            }}
            QLineEdit, QTextEdit, QComboBox, QSpinBox {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                padding: 5px 8px;
                font-size: 11px;
            }}
            QGroupBox {{
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 5px;
                margin-top: 6px;
                padding-top: 8px;
                font-weight: 600;
                font-size: 11px;
            }}
            QGroupBox::title {{
                subcontrol-origin: margin;
                left: 8px;
                padding: 0 4px;
            }}
            QListWidget {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                padding: 3px;
                font-size: 11px;
            }}
            QListWidget::item {{
                padding: 4px;
                border-radius: 3px;
            }}
            QListWidget::item:selected {{
                background-color: rgba(74, 222, 128, 0.2);
                color: {ModernColors.PRIMARY};
            }}
            QProgressBar {{
                border: none;
                border-radius: 3px;
                background-color: {ModernColors.SURFACE};
                height: 5px;
            }}
            QProgressBar::chunk {{
                background: {ModernColors.PRIMARY_GRADIENT};
                border-radius: 3px;
            }}
            QTabWidget::pane {{
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                background-color: {ModernColors.SURFACE};
                padding: 6px;
            }}
            QTabBar::tab {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT_SECONDARY};
                border: 1px solid {ModernColors.BORDER};
                border-bottom: none;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
                padding: 5px 10px;
                margin-right: 2px;
                font-size: 11px;
            }}
            QTabBar::tab:selected {{
                background-color: {ModernColors.SURFACE_LIGHT};
                color: {ModernColors.PRIMARY};
                border-bottom: 2px solid {ModernColors.PRIMARY};
            }}
            QScrollArea {{
                border: none;
                background-color: transparent;
            }}
        """)

        central = QWidget()
        self.setCentralWidget(central)
        main_layout = QHBoxLayout(central)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        self.setup_sidebar(main_layout)
        self.setup_content(main_layout)

    def setup_sidebar(self, main_layout):
        sidebar = QWidget()
        sidebar.setFixedWidth(130)
        sidebar.setStyleSheet(f"""
            QWidget {{
                background-color: {ModernColors.SURFACE};
                border-right: 1px solid {ModernColors.BORDER};
            }}
        """)

        layout = QVBoxLayout(sidebar)
        layout.setContentsMargins(0, 12, 0, 12)
        layout.setSpacing(1)

        title = QLabel("DustMC")
        title.setStyleSheet(f"""
            font-size: 18px;
            font-weight: 700;
            color: {ModernColors.PRIMARY};
            padding: 6px;
            margin-bottom: 6px;
        """)
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(title)

        self.home_btn = SidebarButton("Home")
        self.versions_btn = SidebarButton("Versions")
        self.install_btn = SidebarButton("Install")
        self.modpacks_btn = SidebarButton("Modpacks")
        self.launch_btn = SidebarButton("Launch")
        self.settings_btn = SidebarButton("Settings")
        self.console_btn = SidebarButton("Console")
        self.info_btn = SidebarButton("Information")

        self.home_btn.setChecked(True)

        self.home_btn.clicked.connect(lambda: self.change_page(0))
        self.versions_btn.clicked.connect(lambda: self.change_page(1))
        self.install_btn.clicked.connect(lambda: self.change_page(2))
        self.modpacks_btn.clicked.connect(lambda: self.change_page(3))
        self.launch_btn.clicked.connect(lambda: self.change_page(4))
        self.settings_btn.clicked.connect(lambda: self.change_page(5))
        self.console_btn.clicked.connect(lambda: self.change_page(6))
        self.info_btn.clicked.connect(lambda: self.change_page(7))

        layout.addWidget(self.home_btn)
        layout.addWidget(self.versions_btn)
        layout.addWidget(self.install_btn)
        layout.addWidget(self.modpacks_btn)
        layout.addWidget(self.launch_btn)
        layout.addWidget(self.settings_btn)

        console_row = QWidget()
        console_row.setStyleSheet("QWidget { background: transparent; border: none; }")
        console_row_layout = QHBoxLayout(console_row)
        console_row_layout.setContentsMargins(0, 0, 4, 0)
        console_row_layout.setSpacing(2)
        console_row_layout.addWidget(self.console_btn)

        self.log_corner_btn = QPushButton("log")
        self.log_corner_btn.setFixedSize(26, 26)
        self.log_corner_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.log_corner_btn.setToolTip("Open emce.log in external terminal")
        self.log_corner_btn.setStyleSheet(f"""
            QPushButton {{
                background-color: {ModernColors.SURFACE_LIGHT};
                color: {ModernColors.TEXT_SECONDARY};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 3px;
                font-size: 8px;
                font-weight: 600;
            }}
            QPushButton:hover {{
                border: 1px solid {ModernColors.ACCENT};
                color: {ModernColors.TEXT};
                background-color: {ModernColors.SURFACE};
            }}
        """)
        self.log_corner_btn.clicked.connect(self._open_log_terminal)
        console_row_layout.addWidget(self.log_corner_btn)
        layout.addWidget(console_row)

        layout.addWidget(self.info_btn)
        layout.addStretch()

        version_label = QLabel("v1.0.0")
        version_label.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 9px; padding: 6px;")
        version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(version_label)

        main_layout.addWidget(sidebar)

    def setup_content(self, main_layout):
        self.stack = QStackedWidget()
        self.stack.setStyleSheet(f"""
            QStackedWidget {{
                background-color: {ModernColors.BACKGROUND};
            }}
        """)

        self.create_home_page()
        self.create_versions_page()
        self.create_install_page()
        self.create_modpacks_page()
        self.create_launch_page()
        self.create_settings_page()
        self.create_console_page()
        self.create_information_page()

        main_layout.addWidget(self.stack)

    def create_home_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(12)

        header = QFrame()
        header.setStyleSheet("QFrame { background: transparent; border: none; }")
        header_layout = QVBoxLayout(header)
        header_layout.setContentsMargins(0, 0, 0, 0)
        header_layout.setSpacing(2)

        welcome = QLabel("Welcome back, Player!")
        welcome.setStyleSheet(f"font-size: 18px; font-weight: 700; color: {ModernColors.PRIMARY}; border: none; padding: 0px;")
        header_layout.addWidget(welcome)

        welcome_sub = QLabel("Ready to play? Select a version and launch!")
        welcome_sub.setStyleSheet(f"font-size: 11px; font-weight: 400; color: {ModernColors.TEXT_SECONDARY}; border: none; padding: 0px;")
        header_layout.addWidget(welcome_sub)

        layout.addWidget(header)

        gallery_label = QLabel("Featured Images")
        gallery_label.setStyleSheet(f"font-size: 13px; font-weight: 600; color: {ModernColors.TEXT};")
        layout.addWidget(gallery_label)

        images_layout = QHBoxLayout()
        images_layout.setSpacing=10

        for url in self.image_urls:
            card = ImageCard(url)
            images_layout.addWidget(card, 1)

        layout.addLayout(images_layout)

        quick_actions = QLabel("Quick Actions")
        quick_actions.setStyleSheet(f"font-size: 13px; font-weight: 600; color: {ModernColors.TEXT}; margin-top: 6px;")
        layout.addWidget(quick_actions)

        actions_layout = QHBoxLayout()
        actions_layout.setSpacing(8)

        play_btn = ModernButton("Play Now", primary=True)
        play_btn.clicked.connect(lambda: self.change_page(4))

        install_btn = ModernButton("Install Modpack")
        install_btn.clicked.connect(lambda: self.change_page(3))

        refresh_btn = ModernButton("Refresh")
        refresh_btn.clicked.connect(self.load_data)

        actions_layout.addWidget(play_btn)
        actions_layout.addWidget(install_btn)
        actions_layout.addWidget(refresh_btn)
        actions_layout.addStretch()

        layout.addLayout(actions_layout)

        last_played_label = QLabel("Last Played")
        last_played_label.setStyleSheet(f"font-size: 13px; font-weight: 600; color: {ModernColors.TEXT}; margin-top: 6px;")
        layout.addWidget(last_played_label)

        self.last_played_frame = QFrame()
        self.last_played_frame.setStyleSheet(f"""
            QFrame {{
                background-color: {ModernColors.SURFACE};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
            }}
        """)
        lp_layout = QHBoxLayout(self.last_played_frame)
        lp_layout.setContentsMargins(10, 8, 10, 8)
        lp_layout.setSpacing(10)

        self.last_played_name_label = QLabel("No instance played yet")
        self.last_played_name_label.setStyleSheet(f"font-size: 11px; color: {ModernColors.TEXT_SECONDARY};")
        lp_layout.addWidget(self.last_played_name_label)
        lp_layout.addStretch()

        self.last_played_launch_btn = ModernButton("Launch", primary=True)
        self.last_played_launch_btn.setFixedWidth(70)
        self.last_played_launch_btn.setEnabled(False)
        self.last_played_launch_btn.clicked.connect(self._launch_last_played)
        lp_layout.addWidget(self.last_played_launch_btn)

        layout.addWidget(self.last_played_frame)

        news_label = QLabel("Latest News")
        news_label.setStyleSheet(f"font-size: 13px; font-weight: 600; color: {ModernColors.TEXT}; margin-top: 6px;")
        layout.addWidget(news_label)

        self.news_widget = QListWidget()
        self.news_widget.setMaximumHeight(80)
        layout.addWidget(self.news_widget)

        layout.addStretch()
        self.stack.addWidget(scroll)

    def create_versions_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        title = QLabel("Installed Versions")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        self.versions_list = QListWidget()
        self.versions_list.setMaximumHeight(150)
        self.versions_list.itemClicked.connect(self.on_version_selected)
        layout.addWidget(self.versions_list)

        info_group = QGroupBox("Version Information")
        info_layout = QVBoxLayout()

        self.version_path_label = QLabel("Directory: -")
        self.version_size_label = QLabel("Size: -")
        self.version_type_label = QLabel("Type: -")
        self.version_date_label = QLabel("Last Modified: -")

        info_layout.addWidget(self.version_path_label)
        info_layout.addWidget(self.version_size_label)
        info_layout.addWidget(self.version_type_label)
        info_layout.addWidget(self.version_date_label)

        info_group.setLayout(info_layout)
        layout.addWidget(info_group)

        delete_btn = ModernButton("Delete Selected")
        delete_btn.clicked.connect(self.delete_version)
        layout.addWidget(delete_btn)

        layout.addStretch()
        self.stack.addWidget(scroll)

    def create_install_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        title = QLabel("Install Mod Loader")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        loader_group = QGroupBox("Loader Selection")
        loader_layout = QVBoxLayout()

        loader_layout.addWidget(QLabel("Mod Loader:"))
        self.loader_combo = QComboBox()
        self.loader_combo.currentIndexChanged.connect(self.on_loader_changed)
        loader_layout.addWidget(self.loader_combo)

        loader_layout.addWidget(QLabel("Minecraft Version:"))
        self.version_combo = QComboBox()
        loader_layout.addWidget(self.version_combo)

        loader_group.setLayout(loader_layout)
        layout.addWidget(loader_group)

        dir_group = QGroupBox("Installation Directory")
        dir_layout = QVBoxLayout()

        dir_layout.addWidget(QLabel("Base Directory:"))
        dir_edit_layout = QHBoxLayout()
        self.base_dir_edit = QLineEdit()
        self.base_dir_edit.setText(self.base_minecraft_dir)
        self.base_dir_edit.setReadOnly(True)
        dir_edit_layout.addWidget(self.base_dir_edit)

        browse_btn = ModernButton("Browse")
        browse_btn.setFixedWidth(70)
        browse_btn.clicked.connect(self.browse_base_dir)
        dir_edit_layout.addWidget(browse_btn)

        dir_layout.addLayout(dir_edit_layout)
        dir_group.setLayout(dir_layout)
        layout.addWidget(dir_group)

        self.install_btn_page = ModernButton("Install Mod Loader", primary=True)
        self.install_btn_page.clicked.connect(self.install_modloader)
        self.install_btn_page.setEnabled(False)
        layout.addWidget(self.install_btn_page)

        status_group = QGroupBox("Installation Status")
        status_layout = QVBoxLayout()

        self.install_status = QTextEdit()
        self.install_status.setReadOnly(True)
        self.install_status.setMaximumHeight(60)
        self.install_status.setStyleSheet(f"""
            QTextEdit {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                font-family: 'Courier New', monospace;
                font-size: 10px;
                padding: 4px 6px;
            }}
        """)
        status_layout.addWidget(self.install_status)

        self.install_progress = QProgressBar()
        self.install_progress.setVisible(False)
        status_layout.addWidget(self.install_progress)

        status_group.setLayout(status_layout)
        layout.addWidget(status_group)

        layout.addStretch()
        self.stack.addWidget(scroll)

    def create_modpacks_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        title = QLabel("Install Modpacks")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        tab_widget = QTabWidget()

        mrpack_tab = self.create_mrpack_tab()
        tab_widget.addTab(mrpack_tab, "Modrinth (.mrpack)")

        layout.addWidget(tab_widget)

        self.mrpack_status = QTextEdit()
        self.mrpack_status.setReadOnly(True)
        self.mrpack_status.setMaximumHeight(60)
        self.mrpack_status.setStyleSheet(f"""
            QTextEdit {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                font-family: 'Courier New', monospace;
                font-size: 10px;
                padding: 4px 6px;
            }}
        """)
        layout.addWidget(self.mrpack_status)

        self.stack.addWidget(scroll)

    def create_mrpack_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)

        file_layout = QHBoxLayout()
        self.mrpack_path = QLineEdit()
        self.mrpack_path.setPlaceholderText("Select .mrpack file...")
        file_layout.addWidget(self.mrpack_path)

        browse_btn = ModernButton("Browse")
        browse_btn.setFixedWidth(70)
        browse_btn.clicked.connect(self.browse_mrpack)
        file_layout.addWidget(browse_btn)
        layout.addLayout(file_layout)

        dir_layout = QHBoxLayout()
        dir_layout.addWidget(QLabel("Instances Directory:"))
        self.mrpack_dir = QLineEdit()
        self.mrpack_dir.setText(self.instances_dir)
        self.mrpack_dir.setReadOnly(True)
        self.mrpack_dir.setStyleSheet(
            f"background-color: {ModernColors.SURFACE}; color: {ModernColors.TEXT_SECONDARY};"
            f"border: 1px solid {ModernColors.BORDER}; border-radius: 4px; padding: 5px 8px;"
        )
        dir_layout.addWidget(self.mrpack_dir)
        layout.addLayout(dir_layout)

        hint = QLabel("Each modpack will be installed in its own isolated folder inside Instances Directory.")
        hint.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;")
        hint.setWordWrap(True)
        layout.addWidget(hint)

        self.pack_info = QGroupBox("Pack Information")
        info_layout = QVBoxLayout()
        self.pack_name_label = QLabel("Name: -")
        self.pack_version_label = QLabel("Version: -")
        self.pack_mc_version_label = QLabel("Minecraft: -")
        self.pack_install_path_label = QLabel("Install path: -")
        self.pack_install_path_label.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px;")
        self.pack_install_path_label.setWordWrap(True)
        info_layout.addWidget(self.pack_name_label)
        info_layout.addWidget(self.pack_version_label)
        info_layout.addWidget(self.pack_mc_version_label)
        info_layout.addWidget(self.pack_install_path_label)
        self.pack_info.setLayout(info_layout)
        layout.addWidget(self.pack_info)

        self.optional_group = QGroupBox("Optional Files")
        self.optional_layout = QVBoxLayout()
        self.optional_group.setLayout(self.optional_layout)
        layout.addWidget(self.optional_group)

        self.install_mrpack_btn = ModernButton("Install MRPack", primary=True)
        self.install_mrpack_btn.clicked.connect(self.install_mrpack)
        self.install_mrpack_btn.setEnabled(False)
        layout.addWidget(self.install_mrpack_btn)

        layout.addStretch()
        return tab

    def create_launch_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)

        outer = QHBoxLayout(page)
        outer.setContentsMargins(15, 15, 15, 15)
        outer.setSpacing(12)

        left = QWidget()
        layout = QVBoxLayout(left)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(10)

        title = QLabel("Launch Game")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        version_group = QGroupBox("Version / Instance")
        version_layout = QVBoxLayout()
        version_layout.addWidget(QLabel("Select version or modpack instance:"))
        self.launch_version = QComboBox()
        self.launch_version.currentIndexChanged.connect(self._on_launch_index_changed)
        version_layout.addWidget(self.launch_version)

        self.launch_instance_info = QLabel("")
        self.launch_instance_info.setStyleSheet(
            f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;"
        )
        self.launch_instance_info.setWordWrap(True)
        version_layout.addWidget(self.launch_instance_info)
        version_group.setLayout(version_layout)
        layout.addWidget(version_group)

        settings_group = QGroupBox("Launch Settings")
        settings_layout = QVBoxLayout()

        settings_layout.addWidget(QLabel("Username:"))
        self.username = QLineEdit()
        self.username.setText("Player")
        settings_layout.addWidget(self.username)

        ram_layout = QHBoxLayout()
        ram_layout.addWidget(QLabel("RAM (MB):"))
        self.ram = QSpinBox()
        self.ram.setRange(512, 32768)
        self.ram.setValue(2048)
        self.ram.setSingleStep(512)
        ram_layout.addWidget(self.ram)
        settings_layout.addLayout(ram_layout)

        res_layout = QHBoxLayout()
        res_layout.addWidget(QLabel("Resolution:"))
        self.width = QSpinBox()
        self.width.setRange(640, 3840)
        self.width.setValue(854)
        res_layout.addWidget(self.width)
        res_layout.addWidget(QLabel("x"))
        self.height = QSpinBox()
        self.height.setRange(480, 2160)
        self.height.setValue(480)
        res_layout.addWidget(self.height)
        settings_layout.addLayout(res_layout)

        settings_layout.addWidget(QLabel("JVM Arguments:"))
        self.jvm_args = QLineEdit()
        self.jvm_args.setPlaceholderText("-XX:+UseG1GC ...")
        settings_layout.addWidget(self.jvm_args)

        settings_group.setLayout(settings_layout)
        layout.addWidget(settings_group)

        button_layout = QHBoxLayout()
        self.launch_btn_page = ModernButton("Launch Game", primary=True)
        self.launch_btn_page.clicked.connect(self.launch_minecraft)
        self.stop_btn = ModernButton("Stop Game")
        self.stop_btn.setEnabled(False)
        self.stop_btn.clicked.connect(self.stop_minecraft)
        button_layout.addWidget(self.launch_btn_page)
        button_layout.addWidget(self.stop_btn)
        layout.addLayout(button_layout)

        self.launch_status = QTextEdit()
        self.launch_status.setReadOnly(True)
        self.launch_status.setMaximumHeight(50)
        self.launch_status.setStyleSheet(f"""
            QTextEdit {{
                background-color: {ModernColors.SURFACE};
                color: {ModernColors.TEXT};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                font-family: 'Courier New', monospace;
                font-size: 10px;
                padding: 4px 6px;
            }}
        """)
        layout.addWidget(self.launch_status)
        layout.addStretch()

        outer.addWidget(left, 2)

        right = QWidget()
        right_layout = QVBoxLayout(right)
        right_layout.setContentsMargins(0, 0, 0, 0)
        right_layout.setSpacing(8)

        mod_title = QLabel("Mod Manager")
        mod_title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        right_layout.addWidget(mod_title)

        self.mod_instance_label = QLabel("No instance selected")
        self.mod_instance_label.setStyleSheet(
            f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;"
        )
        self.mod_instance_label.setWordWrap(True)
        right_layout.addWidget(self.mod_instance_label)

        self.instance_mods_list = QListWidget()
        self.instance_mods_list.setStyleSheet(f"""
            QListWidget {{
                background-color: {ModernColors.SURFACE};
                border: 1px solid {ModernColors.BORDER};
                border-radius: 4px;
                font-size: 11px;
            }}
            QListWidget::item {{ padding: 4px; border-radius: 3px; }}
            QListWidget::item:selected {{
                background-color: rgba(74,222,128,0.2);
                color: {ModernColors.PRIMARY};
            }}
        """)
        right_layout.addWidget(self.instance_mods_list, 1)

        mod_btn_layout = QHBoxLayout()
        add_mod_btn = ModernButton("Add Mod")
        add_mod_btn.clicked.connect(self.add_mod_to_instance)
        remove_mod_btn = ModernButton("Remove")
        remove_mod_btn.clicked.connect(self.remove_mod_from_instance)
        open_folder_btn = ModernButton("Open Folder")
        open_folder_btn.clicked.connect(self.open_instance_mods_folder)
        mod_btn_layout.addWidget(add_mod_btn)
        mod_btn_layout.addWidget(remove_mod_btn)
        mod_btn_layout.addWidget(open_folder_btn)
        right_layout.addLayout(mod_btn_layout)

        mod_status_lbl = QLabel("Drag a .jar mod file, or use Add Mod to install into the selected instance.")
        mod_status_lbl.setStyleSheet(
            f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;"
        )
        mod_status_lbl.setWordWrap(True)
        right_layout.addWidget(mod_status_lbl)

        outer.addWidget(right, 1)

        self.stack.addWidget(scroll)

    def create_settings_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        title = QLabel("Settings")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        dir_group = QGroupBox("Directories")
        dir_layout = QVBoxLayout()

        dir_layout.addWidget(QLabel("Base Minecraft Directory (library/assets):"))
        dir_edit_layout = QHBoxLayout()
        self.settings_dir = QLineEdit()
        self.settings_dir.setText(self.base_minecraft_dir)
        dir_edit_layout.addWidget(self.settings_dir)
        browse_btn = ModernButton("Browse")
        browse_btn.setFixedWidth(70)
        browse_btn.clicked.connect(self.browse_settings_dir)
        dir_edit_layout.addWidget(browse_btn)
        dir_layout.addLayout(dir_edit_layout)

        dir_layout.addWidget(QLabel("Instances Directory (modpack containers):"))
        inst_edit_layout = QHBoxLayout()
        self.instances_dir_edit = QLineEdit()
        self.instances_dir_edit.setText(self.instances_dir)
        inst_edit_layout.addWidget(self.instances_dir_edit)
        browse_inst_btn = ModernButton("Browse")
        browse_inst_btn.setFixedWidth(70)
        browse_inst_btn.clicked.connect(self.browse_instances_dir)
        inst_edit_layout.addWidget(browse_inst_btn)
        dir_layout.addLayout(inst_edit_layout)

        dir_group.setLayout(dir_layout)
        layout.addWidget(dir_group)

        config_path_label = QLabel(f"Config file: {CONFIG_PATH}")
        config_path_label.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;")
        config_path_label.setWordWrap(True)
        layout.addWidget(config_path_label)

        console_group = QGroupBox("Console Settings")
        console_layout = QVBoxLayout()
        self.auto_show_console = QCheckBox("Auto-switch to Console tab when game starts")
        self.auto_show_console.setChecked(True)
        console_layout.addWidget(self.auto_show_console)
        console_group.setLayout(console_layout)
        layout.addWidget(console_group)

        java_group = QGroupBox("Java Runtime")
        java_layout = QVBoxLayout()
        java_layout.setSpacing(6)
        self.use_portable_java_check = QCheckBox("Use portable Java runtime bundled with launcher")
        self.use_portable_java_check.setChecked(True)
        java_layout.addWidget(self.use_portable_java_check)
        portable_path = os.path.join(
            self.base_minecraft_dir,
            "runtime", "java-runtime-delta", "windows-x64", "java-runtime-delta", "bin", "java.exe"
        )
        java_hint = QLabel(
            f"Portable runtime path: ...\\coklat\\runtime\\java-runtime-delta\\windows-x64\\java-runtime-delta\\bin\\java.exe\n"
            f"When unchecked, the system Java from PATH is used instead."
        )
        java_hint.setWordWrap(True)
        java_hint.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 10px; font-style: italic;")
        java_layout.addWidget(java_hint)
        java_group.setLayout(java_layout)
        layout.addWidget(java_group)

        appearance_group = QGroupBox("Appearance")
        appearance_layout = QVBoxLayout()
        self.dark_mode_check = QCheckBox("Dark Mode (Always Enabled)")
        self.dark_mode_check.setChecked(True)
        self.dark_mode_check.setEnabled(False)
        appearance_layout.addWidget(self.dark_mode_check)
        appearance_group.setLayout(appearance_layout)
        layout.addWidget(appearance_group)

        save_btn = ModernButton("Save Settings", primary=True)
        save_btn.clicked.connect(self.save_settings)
        layout.addWidget(save_btn)

        clear_cache_btn = ModernButton("Clear Cache")
        clear_cache_btn.clicked.connect(self.clear_cache)
        layout.addWidget(clear_cache_btn)

        layout.addStretch()
        self.stack.addWidget(scroll)

    def create_console_page(self):
        self.console_page = ConsolePage(self.emitter, log_path)
        self.stack.addWidget(self.console_page)

    def _open_log_terminal(self):
        """Open emce.log in an external PowerShell window with live tail."""
        try:
            if not os.path.isfile(log_path):
                open(log_path, "a").close()
            cmd = (
                f'start powershell -NoExit -Command '
                f'"Get-Content -Path \'{log_path}\' -Wait -Tail 80"'
            )
            subprocess.Popen(cmd, shell=True)
            logger.info(f"[CONSOLE] Opened log file in external terminal: {log_path}")
        except Exception as e:
            logger.error(f"[ERROR] Failed to open log terminal: {e}")

    def update_last_played_display(self):
        """Refresh the Last Played widget on the home page."""
        last = self.config.get("last_played_instance", "")
        if last:
            self.last_played_name_label.setText(last)
            self.last_played_name_label.setStyleSheet(
                f"font-size: 11px; color: {ModernColors.TEXT}; font-weight: 500;"
            )
            self.last_played_launch_btn.setEnabled(True)
        else:
            self.last_played_name_label.setText("No instance played yet")
            self.last_played_name_label.setStyleSheet(
                f"font-size: 11px; color: {ModernColors.TEXT_SECONDARY};"
            )
            self.last_played_launch_btn.setEnabled(False)

    def _launch_last_played(self):
        """Switch to Launch page and select the last played instance, then launch."""
        last = self.config.get("last_played_instance", "")
        if not last:
            return
        self.change_page(4)

        for i in range(self.launch_version.count()):
            if self.launch_version.itemData(i) == last:
                self.launch_version.setCurrentIndex(i)
                break
        self.launch_minecraft()

    def create_information_page(self):
        page = QWidget()
        scroll = QScrollArea()
        scroll.setWidget(page)
        scroll.setWidgetResizable(True)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

        layout = QVBoxLayout(page)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(12)

        title = QLabel("Information")
        title.setStyleSheet(f"font-size: 16px; font-weight: 700; color: {ModernColors.PRIMARY};")
        layout.addWidget(title)

        about_group = QGroupBox("About DustMC Launcher")
        about_layout = QVBoxLayout()
        about_text = QLabel(
            "DustMC Launcher is a custom Minecraft launcher built for simplicity and flexibility. "
            "It supports vanilla Minecraft, mod loaders, and modpack installation via the Modrinth .mrpack format. "
            "Each modpack runs in its own isolated instance, keeping your game directories clean and organised. "
            "The launcher is built with Python and PyQt6."
        )
        about_text.setWordWrap(True)
        about_text.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 11px;")
        about_layout.addWidget(about_text)
        about_group.setLayout(about_layout)
        layout.addWidget(about_group)

        devs_group = QGroupBox("Developers")
        devs_layout = QVBoxLayout()
        devs_layout.setSpacing(8)

        def make_dev_card(name, role):
            card = QFrame()
            card.setStyleSheet(f"""
                QFrame {{
                    background-color: {ModernColors.SURFACE_LIGHT};
                    border: none;
                    border-radius: 4px;
                }}
            """)
            card_layout = QVBoxLayout(card)
            card_layout.setContentsMargins(10, 8, 10, 8)
            card_layout.setSpacing(2)
            name_lbl = QLabel(name)
            name_lbl.setStyleSheet(f"font-size: 12px; font-weight: 600; color: {ModernColors.TEXT}; border: none; padding: 0px;")
            role_lbl = QLabel(role)
            role_lbl.setStyleSheet(f"font-size: 11px; color: {ModernColors.TEXT_SECONDARY}; border: none; padding: 0px;")
            card_layout.addWidget(name_lbl)
            card_layout.addWidget(role_lbl)
            return card

        devs_layout.addWidget(make_dev_card("Yhujin", "Interface, main functions, designer"))
        devs_layout.addWidget(make_dev_card("Milk", "Modpack installer"))
        devs_group.setLayout(devs_layout)
        layout.addWidget(devs_group)

        community_group = QGroupBox("Community")
        community_layout = QVBoxLayout()
        community_layout.setSpacing(8)
        community_desc = QLabel(
            "Join the Discord server to get support, report issues, and follow new releases."
        )
        community_desc.setWordWrap(True)
        community_desc.setStyleSheet(f"color: {ModernColors.TEXT_SECONDARY}; font-size: 11px;")
        community_layout.addWidget(community_desc)
        discord_btn = ModernButton("Join Discord Server")
        discord_btn.setFixedWidth(160)
        discord_btn.clicked.connect(lambda: __import__("webbrowser").open("https://discord.com/invite/4K8eJztN7W"))
        community_layout.addWidget(discord_btn)
        community_group.setLayout(community_layout)
        layout.addWidget(community_group)

        layout.addStretch()
        self.stack.addWidget(scroll)

    def change_page(self, index):
        self.stack.setCurrentIndex(index)
        buttons = [self.home_btn, self.versions_btn, self.install_btn,
                   self.modpacks_btn, self.launch_btn, self.settings_btn,
                   self.console_btn, self.info_btn]
        for i, btn in enumerate(buttons):
            btn.setChecked(i == index)

        if index == 0:
            self.load_news()
        elif index == 1:
            self.load_installed_versions()
        elif index == 2:
            self.load_mod_loaders()
        elif index == 3:
            self.load_mods_list()
        elif index == 4:
            self.load_launch_versions()
        elif index == 6:
            pass

    def load_data(self):
        self.load_mod_loaders()
        self.load_installed_versions()
        self.load_mods_list()
        self.load_news()

    def load_news(self):
        self.news_worker = NewsWorker(self.emitter)
        self.news_worker.start()

    def load_mod_loaders(self):
        try:
            self.loader_list = minecraft_launcher_lib.mod_loader.list_mod_loader()
            names = [minecraft_launcher_lib.mod_loader.get_mod_loader(i).get_name()
                    for i in self.loader_list]
            self.loader_combo.clear()
            self.loader_combo.addItems(names)
        except Exception as e:
            self.update_status(f"Error: {e}")

    def load_installed_versions(self):
        try:
            logger.debug(f"[READ] Scanning installed versions in: {self.base_minecraft_dir}")
            versions = minecraft_launcher_lib.utils.get_installed_versions(self.base_minecraft_dir)
            self.installed_versions = [v["id"] for v in versions]
            logger.info(f"[READ] Found {len(self.installed_versions)} installed version(s): {self.installed_versions}")
            self.versions_list.clear()
            self.versions_list.addItems(self.installed_versions)
            self.emitter.versions_loaded.emit(self.installed_versions)
        except Exception as e:
            logger.error(f"[ERROR] load_installed_versions failed: {e}")
            self.update_status(f"Error: {e}")

    def load_mods_list(self):
        try:
            mods_dir = os.path.join(self.base_minecraft_dir, "mods")
            logger.debug(f"[READ] Scanning mods directory: {mods_dir}")
            if os.path.exists(mods_dir):
                mods = [f for f in os.listdir(mods_dir) if f.endswith('.jar')]
                logger.info(f"[READ] Found {len(mods)} mod(s) in {mods_dir}")
                self.mods_list.clear()
                self.mods_list.addItems(mods)
        except Exception as e:
            logger.warning(f"[READ] Failed to scan mods dir: {e}")

    def load_launch_versions(self):
        """Populate launch dropdown with installed MC versions + modpack instances."""
        self.launch_version.clear()

        if self.installed_versions:
            for v in self.installed_versions:
                self.launch_version.addItem(f"[MC] {v}", v)

        if os.path.isdir(self.instances_dir):
            instances = []
            for name in sorted(os.listdir(self.instances_dir)):
                inst_json = os.path.join(self.instances_dir, name, "instance.json")
                if os.path.isfile(inst_json):
                    instances.append(name)
            if instances:
                if self.installed_versions:
                    self.launch_version.insertSeparator(self.launch_version.count())
                for name in instances:
                    self.launch_version.addItem(f"[Pack] {name}", name)
                logger.debug(f"[READ] Found {len(instances)} instance(s): {instances}")

        last = self.config.get("last_played_instance", "")
        selected_index = 0
        if last:
            for i in range(self.launch_version.count()):
                if self.launch_version.itemData(i) == last:
                    selected_index = i
                    break
        if self.launch_version.count() > 0:
            self.launch_version.setCurrentIndex(selected_index)
            self.on_launch_version_changed(
                self.launch_version.itemData(selected_index) or self.launch_version.currentText()
            )

    def update_news(self, news):
        self.news_widget.clear()
        for item in news:
            self.news_widget.addItem(f"{item['title']} - {item['date']}")

    def on_version_selected(self, item):
        version = item.text()
        version_dir = os.path.join(self.base_minecraft_dir, "versions", version)
        self.version_path_label.setText(f"Directory: {version_dir}")
        logger.info(f"[READ] Inspecting version directory: {version_dir}")

        try:
            if os.path.exists(version_dir):
                size = sum(f.stat().st_size for f in Path(version_dir).rglob('*') if f.is_file()) / (1024*1024)
                self.version_size_label.setText(f"Size: {size:.1f} MB")
                logger.debug(f"[READ] Version '{version}' size: {size:.1f} MB")

                mod_time = os.path.getmtime(version_dir)
                mod_date = datetime.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M")
                self.version_date_label.setText(f"Last Modified: {mod_date}")
        except Exception as e:
            logger.warning(f"[READ] Could not read version dir stats for '{version}': {e}")
            self.version_size_label.setText("Size: Unknown")

        json_path = os.path.join(version_dir, f"{version}.json")
        if os.path.exists(json_path):
            try:
                logger.info(f"[READ] Reading version JSON: {json_path}")
                with open(json_path) as f:
                    data = json.load(f)
                self.version_type_label.setText(f"Type: {data.get('type', 'Unknown')}")
                logger.debug(f"[READ] Version type: {data.get('type', 'Unknown')}")
            except Exception as e:
                logger.warning(f"[READ] Failed to parse version JSON {json_path}: {e}")
                self.version_type_label.setText("Type: Unknown")

    def delete_version(self):
        current = self.versions_list.currentItem()
        if not current:
            QMessageBox.warning(self, "Warning", "Please select a version to delete")
            return

        version = current.text()
        reply = QMessageBox.question(self, "Confirm Delete",
                                    f"Are you sure you want to delete {version}?",
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)

        if reply == QMessageBox.StandardButton.Yes:
            try:
                version_dir = os.path.join(self.base_minecraft_dir, "versions", version)
                logger.warning(f"[DELETE] Deleting version directory: {version_dir}")
                if os.path.exists(version_dir):
                    shutil.rmtree(version_dir)
                    logger.info(f"[DELETE] Version '{version}' deleted successfully")
                self.load_installed_versions()
                QMessageBox.information(self, "Success", f"Version {version} deleted")
            except Exception as e:
                logger.error(f"[ERROR] Failed to delete version '{version}': {e}")
                QMessageBox.critical(self, "Error", f"Failed to delete: {str(e)}")

    def delete_mod(self):
        current = self.mods_list.currentItem()
        if not current:
            QMessageBox.warning(self, "Warning", "Please select a mod to delete")
            return

        mod = current.text()
        reply = QMessageBox.question(self, "Confirm Delete",
                                    f"Are you sure you want to delete {mod}?",
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)

        if reply == QMessageBox.StandardButton.Yes:
            try:
                mod_path = os.path.join(self.base_minecraft_dir, "mods", mod)
                logger.warning(f"[DELETE] Deleting mod file: {mod_path}")
                if os.path.exists(mod_path):
                    os.remove(mod_path)
                    logger.info(f"[DELETE] Mod '{mod}' deleted successfully")
                self.load_mods_list()
                QMessageBox.information(self, "Success", f"Mod {mod} deleted")
            except Exception as e:
                logger.error(f"[ERROR] Failed to delete mod '{mod}': {e}")
                QMessageBox.critical(self, "Error", f"Failed to delete: {str(e)}")

    def on_loader_changed(self, index):
        if index >= 0 and self.loader_list:
            loader_id = self.loader_list[index]
            self.selected_loader = minecraft_launcher_lib.mod_loader.get_mod_loader(loader_id)
            try:
                versions = self.selected_loader.get_minecraft_versions(True)
                self.version_combo.clear()
                self.version_combo.addItems(versions)
                self.install_btn_page.setEnabled(True)
            except Exception as e:
                self.update_status(f"Error: {e}")

    def browse_base_dir(self):
        directory = QFileDialog.getExistingDirectory(self, "Select Base Directory", self.base_minecraft_dir)
        if directory:
            self.base_minecraft_dir = directory
            self.base_dir_edit.setText(directory)

    def browse_mrpack(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select MRPack", "", "MRPack (*.mrpack)")
        if file_path:
            self.mrpack_path.setText(file_path)
            self.load_mrpack_info(file_path)

    def browse_settings_dir(self):
        directory = QFileDialog.getExistingDirectory(self, "Select Base Directory", self.base_minecraft_dir)
        if directory:
            self.settings_dir.setText(directory)

    def load_mrpack_info(self, file_path):
        try:
            logger.info(f"[READ] Loading mrpack info from: {file_path}")
            self.current_mrpack_info = minecraft_launcher_lib.mrpack.get_mrpack_information(file_path)
            name = self.current_mrpack_info.get('name', '-')
            version = self.current_mrpack_info.get('version', '-')
            mc_ver = self.current_mrpack_info.get('minecraftVersion', '-')
            logger.debug(f"[READ] mrpack info: name={name}, version={version}, mc={mc_ver}")
            preview_path = os.path.join(self.instances_dir, name)
            self.pack_name_label.setText(f"Name: {name}")
            self.pack_version_label.setText(f"Version: {version}")
            self.pack_mc_version_label.setText(f"Minecraft: {mc_ver}")
            self.pack_install_path_label.setText(f"Install path: {preview_path}")

            for child in self.optional_layout.children():
                if isinstance(child, (QCheckBox, QLabel)):
                    child.deleteLater()

            optional_files = self.current_mrpack_info.get('optionalFiles', [])
            if optional_files:
                for opt in optional_files:
                    cb = QCheckBox(opt)
                    cb.setStyleSheet(f"color: {ModernColors.TEXT};")
                    self.optional_layout.addWidget(cb)
            else:
                self.optional_layout.addWidget(QLabel("No optional files available"))

            self.install_mrpack_btn.setEnabled(True)
        except Exception as e:
            logger.error(f"[ERROR] load_mrpack_info: {e}")
            QMessageBox.warning(self, "Error", f"Invalid MRPack: {str(e)}")

    def install_modloader(self):
        if not self.selected_loader or not self.version_combo.currentText():
            logger.warning("[WRITE] install_modloader called but no loader or version selected")
            return

        version = self.version_combo.currentText()
        loader_name = self.loader_combo.currentText()
        logger.info(f"[WRITE] ── Starting mod loader install ──────────────────────────")
        logger.info(f"[WRITE]   Loader   : {loader_name}")
        logger.info(f"[WRITE]   MC ver   : {version}")
        logger.info(f"[WRITE]   Base dir : {self.base_minecraft_dir}")
        self.emitter.console_output.emit(f"=== Installing {loader_name} for Minecraft {version} ===")
        self.install_btn_page.setEnabled(False)
        self.install_status.clear()
        self.install_progress.setVisible(True)
        self.install_progress.setValue(0)

        self.worker = InstallWorker(
            self.selected_loader,
            version,
            self.base_minecraft_dir,
            self.emitter
        )
        self.worker.start()

    def install_mrpack(self):
        path = self.mrpack_path.text()
        if not path or not os.path.isfile(path):
            logger.warning(f"[WRITE] install_mrpack called but path invalid: '{path}'")
            return

        java_exe = self.get_java_executable()
        logger.info(f"[WRITE] ── Starting mrpack install ─────────────────────────────")
        logger.info(f"[WRITE]   File         : {path}")
        logger.info(f"[WRITE]   Instances dir: {self.instances_dir}")
        logger.info(f"[WRITE]   Base dir     : {self.base_minecraft_dir}")
        logger.info(f"[JAVA]    Resolved java : {java_exe}")
        self.emitter.console_output.emit(f"=== Installing mrpack: {os.path.basename(path)} ===")
        self.emitter.console_output.emit(f"[JAVA] Using: {java_exe}")
        self.install_mrpack_btn.setEnabled(False)
        self.mrpack_status.clear()

        self.modpack_worker = ModpackInstallWorker(
            path, self.base_minecraft_dir, self.instances_dir, self.emitter, java_exe
        )
        self.modpack_worker.start()

    def install_jar_mod(self):
        pass

    def get_java_executable(self):
        """Return the path to java.exe to use based on the current setting."""
        use_portable = self.use_portable_java_check.isChecked()
        if use_portable:
            java_path = os.path.join(
                self.base_minecraft_dir,
                "runtime", "java-runtime-delta",
                "windows-x64", "java-runtime-delta",
                "bin", "java.exe"
            )
            if os.path.isfile(java_path):
                logger.info(f"[JAVA] Using portable runtime: {java_path}")
                return java_path
            else:
                logger.warning(f"[JAVA] Portable runtime not found at '{java_path}', falling back to system Java")
        logger.info("[JAVA] Using system Java from PATH")
        return "java"

    def launch_minecraft(self):
        index = self.launch_version.currentIndex()
        version = self.launch_version.itemData(index) if index >= 0 else ""
        if not version:
            QMessageBox.warning(self, "Warning", "Please select a version to launch")
            return

        if self.minecraft_process and self.minecraft_process.isRunning():
            QMessageBox.warning(self, "Warning", "Minecraft is already running")
            return

        self.emitter.console_output.emit(f"=== Launching Minecraft {version} ===")
        logger.info(f"[LAUNCH] ── Launch requested ───────────────────────────────────")
        logger.info(f"[LAUNCH]   Version  : {version}")
        logger.info(f"[LAUNCH]   Username : {self.username.text()}")
        logger.info(f"[LAUNCH]   RAM      : {self.ram.value()} MB")
        logger.info(f"[LAUNCH]   Res      : {self.width.value()}x{self.height.value()}")

        try:

            instance_path = os.path.join(self.instances_dir, version)
            instance_json = os.path.join(instance_path, "instance.json")
            if os.path.isfile(instance_json):

                with open(instance_json, "r", encoding="utf-8") as f:
                    inst_meta = json.load(f)
                mc_version = inst_meta.get("minecraftVersion", version)
                game_dir = instance_path
                logger.info(f"[LAUNCH]   Type     : modpack instance")
                logger.info(f"[LAUNCH]   MC ver   : {mc_version}")
                logger.info(f"[LAUNCH]   Game dir : {game_dir}")

                loader_version = inst_meta.get("loaderVersion", "")

                if not loader_version or loader_version == mc_version:
                    for v in self.installed_versions:
                        if v.endswith(f"-{mc_version}") and "-" in v and v != mc_version:
                            loader_version = v
                            logger.info(f"[LAUNCH]   Loader   : auto-detected '{loader_version}'")
                            break

                if not loader_version:
                    loader_version = mc_version
                    logger.info(f"[LAUNCH]   Loader   : fallback to vanilla '{loader_version}'")

                logger.info(f"[LAUNCH] Resolved launch version: {loader_version}")
                version = loader_version
            else:

                game_dir = self.base_minecraft_dir
                logger.info(f"[LAUNCH]   Type     : vanilla / loader")
                logger.info(f"[LAUNCH]   Game dir : {game_dir}")
            options = {
                "username": self.username.text(),
                "uuid": self.config.get("uuid", str(_uuid_mod.uuid4())),
                "token": "",
                "launcherName": "DustMC",
                "gameDirectory": game_dir,
                "version": version,
                "jvmArguments": [f"-Xmx{self.ram.value()}M", f"-Xms{self.ram.value()}M"],
            }

            if self.jvm_args.text().strip():
                extra = self.jvm_args.text().split()
                options["jvmArguments"].extend(extra)
                logger.debug(f"[LAUNCH] Extra JVM args: {extra}")

            options["customResolution"] = "true"
            options["resolutionWidth"] = str(self.width.value())
            options["resolutionHeight"] = str(self.height.value())

            logger.info(f"[READ] Building game command for version '{version}' from: {self.base_minecraft_dir}")
            self.update_launch_status("Starting Minecraft...")

            command = minecraft_launcher_lib.command.get_minecraft_command(
                version,
                self.base_minecraft_dir,
                options
            )

            java_exe = self.get_java_executable()
            original_java = command[0] if command else "?"
            command[0] = java_exe
            logger.info(f"[JAVA]   Library resolved : {original_java}")
            logger.info(f"[JAVA]   Override to      : {java_exe}")
            self.emitter.console_output.emit(f"[JAVA] Using: {java_exe}")

            logger.info(f"[LAUNCH] Command has {len(command)} args | game dir: {game_dir}")
            self.emitter.console_output.emit(f"Command: {' '.join(command)}")

            self.minecraft_process = MinecraftProcess(
                command,
                options["gameDirectory"],
                self.emitter
            )
            self.minecraft_process.start()

            self.launch_btn_page.setEnabled(False)
            self.stop_btn.setEnabled(True)

            if self.auto_show_console.isChecked():
                self.change_page(6)

            self.update_launch_status("Game started successfully!")
            logger.info(f"[LAUNCH] Minecraft {version} started successfully")

            raw_index = self.launch_version.currentIndex()
            raw_name = self.launch_version.itemData(raw_index) or ""
            if raw_name:
                self.config["last_played_instance"] = raw_name
                save_config(self.config)
                self.update_last_played_display()
                logger.info(f"[WRITE] Last played saved: {raw_name}")

        except Exception as e:
            logger.error(f"[ERROR] launch_minecraft failed: {e}")
            self.emitter.console_output.emit(f"Error: {str(e)}")
            self.update_launch_status(f"Error: {str(e)}")

    def stop_minecraft(self):
        if self.minecraft_process:
            self.emitter.console_output.emit("=== Stopping Minecraft ===")
            self.minecraft_process.stop()
            self.minecraft_process.wait()
            self.minecraft_process = None

        self.launch_btn_page.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.update_launch_status("Game stopped")

    def _get_current_mods_dir(self):
        """Return path to mods/ of the currently selected version/instance, or None."""
        version = self.launch_version.currentText()
        if not version:
            return None
        instance_path = os.path.join(self.instances_dir, version)
        instance_json = os.path.join(instance_path, "instance.json")
        if os.path.isfile(instance_json):
            return os.path.join(instance_path, "mods")

        return None

    def _on_launch_index_changed(self, index):
        """Bridge: resolve itemData → name, then call on_launch_version_changed."""
        if index < 0:
            self.on_launch_version_changed("")
            return
        name = self.launch_version.itemData(index)
        if name is None:

            self.on_launch_version_changed("")
            return
        self.on_launch_version_changed(name)

    def on_launch_version_changed(self, version):
        """Called when the launch version combo changes — refresh mod list."""
        self.instance_mods_list.clear()
        if not version:
            self.mod_instance_label.setText("No instance selected")
            self.launch_instance_info.setText("")
            return

        instance_path = os.path.join(self.instances_dir, version)
        instance_json = os.path.join(instance_path, "instance.json")

        if os.path.isfile(instance_json):
            try:
                with open(instance_json, "r", encoding="utf-8") as f:
                    meta = json.load(f)
                mc_ver = meta.get("minecraftVersion", "?")
                pack_ver = meta.get("version", "?")
                inst_type = meta.get("type", "?")
                self.launch_instance_info.setText(
                    f"Instance: {version} | MC {mc_ver} | v{pack_ver} | type: {inst_type}"
                )
                self.mod_instance_label.setText(f"Mods in: {instance_path}\\mods\\")
                logger.debug(f"[READ] Loading mods for instance: {instance_path}")
            except Exception as e:
                logger.warning(f"[READ] Could not read instance.json for {version}: {e}")
                self.mod_instance_label.setText(f"Instance: {version} (could not read metadata)")
                self.launch_instance_info.setText("")

            mods_dir = os.path.join(instance_path, "mods")
            if os.path.isdir(mods_dir):
                mods = sorted(f for f in os.listdir(mods_dir) if f.endswith(".jar"))
                logger.info(f"[READ] Found {len(mods)} mod(s) in {mods_dir}")
                for mod in mods:
                    size_mb = os.path.getsize(os.path.join(mods_dir, mod)) / (1024 * 1024)
                    item = QListWidgetItem(f"{mod}  ({size_mb:.1f} MB)")
                    item.setData(Qt.ItemDataRole.UserRole, mod)
                    self.instance_mods_list.addItem(item)
                if not mods:
                    self.instance_mods_list.addItem("(no mods installed)")
            else:
                self.instance_mods_list.addItem("(mods folder not found)")
        else:

            self.launch_instance_info.setText(f"Vanilla/loader version — mod management not available")
            self.mod_instance_label.setText("Select a modpack instance to manage mods.")

    def add_mod_to_instance(self):
        """Copy one or more .jar files into the selected instance's mods/ folder."""
        mods_dir = self._get_current_mods_dir()
        if not mods_dir:
            QMessageBox.warning(self, "No Instance", "Please select a modpack instance to add mods to.")
            return

        files, _ = QFileDialog.getOpenFileNames(
            self, "Select Mod JAR(s)", "", "JAR files (*.jar)"
        )
        if not files:
            return

        os.makedirs(mods_dir, exist_ok=True)
        added = []
        for src in files:
            dest = os.path.join(mods_dir, os.path.basename(src))
            try:
                shutil.copy2(src, dest)
                logger.info(f"[WRITE] Mod added: {src} → {dest}")
                added.append(os.path.basename(src))
            except Exception as e:
                logger.error(f"[ERROR] Failed to copy mod {src}: {e}")
                QMessageBox.warning(self, "Error", f"Failed to copy {os.path.basename(src)}: {e}")

        if added:
            QMessageBox.information(self, "Added", f"Added {len(added)} mod(s):\n" + "\n".join(added))

            self.on_launch_version_changed(self.launch_version.currentText())

    def remove_mod_from_instance(self):
        """Remove the selected mod from the current instance's mods/ folder."""
        mods_dir = self._get_current_mods_dir()
        if not mods_dir:
            QMessageBox.warning(self, "No Instance", "Please select a modpack instance first.")
            return

        item = self.instance_mods_list.currentItem()
        if not item:
            QMessageBox.warning(self, "No Selection", "Please select a mod to remove.")
            return

        mod_filename = item.data(Qt.ItemDataRole.UserRole)
        if not mod_filename:
            return

        mod_path = os.path.join(mods_dir, mod_filename)
        reply = QMessageBox.question(
            self, "Confirm Delete",
            f"Remove mod '{mod_filename}' from this instance?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                os.remove(mod_path)
                logger.warning(f"[DELETE] Mod removed: {mod_path}")
                self.on_launch_version_changed(self.launch_version.currentText())
                QMessageBox.information(self, "Removed", f"'{mod_filename}' removed.")
            except Exception as e:
                logger.error(f"[ERROR] Failed to remove mod {mod_path}: {e}")
                QMessageBox.critical(self, "Error", f"Failed to remove: {e}")

    def open_instance_mods_folder(self):
        """Open the mods/ folder of the selected instance in Explorer."""
        mods_dir = self._get_current_mods_dir()
        if not mods_dir:
            QMessageBox.warning(self, "No Instance", "Please select a modpack instance first.")
            return
        os.makedirs(mods_dir, exist_ok=True)
        logger.info(f"[READ] Opening mods folder in Explorer: {mods_dir}")
        subprocess.Popen(f'explorer "{mods_dir}"')

    def browse_instances_dir(self):
        directory = QFileDialog.getExistingDirectory(self, "Select Instances Directory", self.instances_dir)
        if directory:
            self.instances_dir = directory
            self.instances_dir_edit.setText(directory)
            self.mrpack_dir.setText(directory)
            logger.info(f"[WRITE] Instances dir changed to: {directory}")

    def save_settings(self):
        old_mc = self.base_minecraft_dir
        old_inst = self.instances_dir

        self.base_minecraft_dir = self.settings_dir.text().strip() or old_mc
        self.instances_dir = self.instances_dir_edit.text().strip() or old_inst

        os.makedirs(self.instances_dir, exist_ok=True)

        self.base_dir_edit.setText(self.base_minecraft_dir)
        self.mrpack_dir.setText(self.instances_dir)

        self.config.update({
            "base_minecraft_dir": self.base_minecraft_dir,
            "instances_dir": self.instances_dir,
            "username": self.username.text(),
            "ram_mb": self.ram.value(),
            "width": self.width.value(),
            "height": self.height.value(),
            "jvm_args": self.jvm_args.text(),
            "auto_show_console": self.auto_show_console.isChecked(),
            "use_portable_java": self.use_portable_java_check.isChecked(),
        })
        save_config(self.config)

        logger.info(f"[WRITE] Settings saved. mc_dir: '{old_mc}'→'{self.base_minecraft_dir}', "
                    f"instances: '{old_inst}'→'{self.instances_dir}'")
        QMessageBox.information(self, "Success", f"Settings saved to {CONFIG_PATH}")

    def clear_cache(self):
        try:
            cache_dir = os.path.join(self.base_minecraft_dir, "cache")
            logger.warning(f"[DELETE] Clearing cache directory: {cache_dir}")
            if os.path.exists(cache_dir):
                shutil.rmtree(cache_dir)
                logger.info(f"[DELETE] Cache cleared: {cache_dir}")
            else:
                logger.info(f"[DELETE] Cache dir not found, nothing to clear: {cache_dir}")
            QMessageBox.information(self, "Success", "Cache cleared successfully")
        except Exception as e:
            logger.error(f"[ERROR] Failed to clear cache: {e}")
            QMessageBox.critical(self, "Error", f"Failed to clear cache: {str(e)}")

    def update_status(self, msg):
        idx = self.stack.currentIndex()
        if idx == 2:
            self.install_status.append(msg)
        elif idx == 3:
            self.mrpack_status.append(msg)

    def update_progress(self, value):
        self.install_progress.setValue(value)

    def update_launch_status(self, msg):
        self.launch_status.append(msg)

    def update_versions_list(self, versions):
        self.launch_version.clear()
        self.launch_version.addItems(versions)

    def on_installation_finished(self):
        self.install_btn_page.setEnabled(True)
        self.install_mrpack_btn.setEnabled(True)
        self.install_progress.setVisible(False)
        self.load_installed_versions()
        self.load_mods_list()
        QMessageBox.information(self, "Success", "Installation completed successfully")

def main():
    app = QApplication(sys.argv)
    app.setStyle('Fusion')

    font = QFont("Segoe UI", 9)
    app.setFont(font)

    window = DustMinecraftLauncher()
    window.show()

    sys.exit(app.exec())

if __name__ == "__main__":
    main()